//
// Created by Administrator on 2021/12/5.
//

#include "Noise.h"

float Noise::quintic_smooth(float t) {
    return std::pow(t, 3.0f) * (t * (t * 6.0f - 15.0f) + 10.0f);
}

float Noise::lerp(float a, float b, float t) {
    return (a * (1.0f - t)) + (b * t);
}

float Noise::gradient(int hash, float x, float y, float z) {
    const int h = hash & 15;
    const float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
    return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}

float Noise::get_value(float x, float y, float z) {
    // Unit coordinates in cube
    const int unit_x = int(std::floor(x)) & 255;
    const int unit_y = int(std::floor(y)) & 255;
    const int unit_z = int(std::floor(z)) & 255;

    // Relative coordinates in cube
    x = x - std::floor(x);
    y = y - std::floor(y);
    z = z - std::floor(z);

    // Compute fading coefficients
    const float u = quintic_smooth(x);
    const float v = quintic_smooth(y);
    const float w = quintic_smooth(z);

    // Hash cube coordinates
    const int a = perm[unit_x] + unit_y;
    const int aa = perm[a] + unit_z;
    const int ab = perm[a + 1] + unit_z;
    const int b = perm[unit_x + 1] + unit_y;
    const int ba = perm[b] + unit_z;
    const int bb = perm[b + 1] + unit_z;

    // Interpolate results
    const float l1 = lerp(gradient(perm[aa], x, y, z), gradient(perm[ba], x - 1, y, z), u);
    const float l2 = lerp(gradient(perm[ab], x, y - 1, z), gradient(perm[bb], x - 1, y - 1, z), u);
    const float l3 = lerp(gradient(perm[aa + 1], x, y, z - 1), gradient(perm[ba + 1], x - 1, y, z - 1), u);
    const float l4 = lerp(gradient(perm[ab + 1], x, y - 1, z - 1), gradient(perm[bb + 1], x - 1, y - 1, z - 1), u);
    const float l5 = lerp(l1, l2, v);
    const float l6 = lerp(l3, l4, v);
    return lerp(l5, l6, w);
}
